////////////////////////////////////////////////////////////////////////////
//	Module 		: smart_cover_animation_selector.cpp
//	Created 	: 07.09.2007
//	Author		: Alexander Dudin
//	Description : Animation selector for smart covers
////////////////////////////////////////////////////////////////////////////

#include "pch_script.h"
#include "smart_cover_animation_selector.h"
#include "ai/stalker/ai_stalker.h"
#include "smart_cover_animation_planner.h"
#include "smart_cover_planner_actions.h"
#include "script_game_object.h"
#include "Include/xrRender/KinematicsAnimated.h"
#include "stalker_animation_manager.h"
#include "smart_cover_planner_actions.h"
#include "stalker_movement_manager_smart_cover.h"
#include "inventory.h"
#include "smart_cover.h"
#include "huditem.h"

float	g_smart_cover_animation_speed_factor = 1.f;

using smart_cover::animation_selector;
using smart_cover::action_base;
using smart_cover::wait_after_exit;

animation_selector::animation_selector				(CAI_Stalker *object) :
	m_object			(object),
	m_callback_called	(false),
	m_first_time		(true),
	m_previous_time		(flt_max)
{
	m_skeleton_animated	= smart_cast<IKinematicsAnimated*>(object->Visual());
	VERIFY				(m_skeleton_animated);
	m_planner			= new animation_planner(object, "animation planner");
}

animation_selector::~animation_selector				()
{
	xr_delete			(m_planner);
}

void animation_selector::initialize					()
{
	m_planner->initialize		();

	// we need this, since we could change bone callbacks
	// which needs animation blends
	// but blends are not accessible since
	// we didn't called animation().update()
	// which is called on UpdateCL
	// and in case of RayQuery
	// our bone callback could be called
	// but UpdateCL is not called yet
	// and we will have 
	// VERIFY	(*parameter->m_blend);
	m_callback_called			= true;
	m_object->animation().update();

	m_first_time				= true;
}

void animation_selector::finalize					()
{
	if (!m_planner->initialized())
		return;

	m_planner->finalize		();
}

action_base *animation_selector::current_operator	() const
{
	return				(&smart_cast<smart_cover::action_base&>(m_planner->current_action()));
}

MotionID animation_selector::select_animation		(bool &animation_movement_controller)
{
	animation_movement_controller	= true;

	if (m_callback_called) {
		if (m_planner->initialized()) {
			current_operator()->on_animation_end();
			m_callback_called	= false;
			
			m_previous_time		= 0.f;
			if (!m_planner->initialized()) {
//				Msg				("%6d no planner update, planner is not initialized, exiting", Device.dwTimeGlobal);
				return			(m_object->animation().assign_global_animation(animation_movement_controller));
			}
		}

//		Msg					("%6d updating planner", Device.dwTimeGlobal);
		m_planner->update	();

		if (!m_planner->initialized()) {
//			Msg				("%6d planner is not initialized after update, exiting", Device.dwTimeGlobal);
			return			(m_object->animation().assign_global_animation(animation_movement_controller));
		}

		current_operator()->on_no_mark	();
		if (!current_operator()->is_animated_action())
			return			(m_object->animation().assign_global_animation(animation_movement_controller));

		current_operator()->select_animation(m_animation);

		VERIFY				( m_object->movement().current_params().cover() );
		if ( !m_object->movement().current_params().cover()->is_combat_cover() )
			return			(m_skeleton_animated->ID_Cycle( m_animation.c_str()));

#ifndef MASTER_GOLD
		if (!psAI_Flags.test((u32)aiUseSmartCoversAnimationSlot))
			return			(m_skeleton_animated->ID_Cycle( m_animation.c_str()));

		VERIFY				( m_object->inventory().ActiveItem() );
		CHudItem* const		hud_item = smart_cast<CHudItem*>(m_object->inventory().ActiveItem());
		VERIFY				( hud_item );

		string16			animation_slot_string;
		R_ASSERT			( !_itoa_s( hud_item->animation_slot(), animation_slot_string, sizeof(animation_slot_string), 10 ) );

		LPSTR				result;
		STRCONCAT			( result, m_animation, "_slot_", animation_slot_string);

		MotionID			animation_id = m_skeleton_animated->ID_Cycle_Safe( result );
		if (animation_id)
			return			(animation_id);

		STRCONCAT			( result, m_animation, "_slot_2" );
		animation_id		= m_skeleton_animated->ID_Cycle_Safe( result );
		VERIFY				(animation_id);
		return				(animation_id);
#else // #ifndef MASTER_GOLD
		return				(m_skeleton_animated->ID_Cycle( m_animation.c_str()));
#endif // #ifndef MASTER_GOLD
	}

	VERIFY				(m_animation._get());
//	VERIFY				(m_first_time || m_object->animation().global().blend());
	MotionID			result = m_skeleton_animated->ID_Cycle( m_animation.c_str());
	if (m_first_time) {
		m_first_time	= false;
		m_previous_time	= 0.f;
		current_operator()->on_no_mark	();
		return			(result);
	}

	CBlend const* const	blend = m_object->animation().global().blend();
	if (!blend) {
		m_previous_time	= 0.f;
		current_operator()->on_no_mark	();
		return			(result);
	}

	VERIFY				(blend->motionID == result);
	CMotionDef			*motion_def = m_skeleton_animated->LL_GetMotionDef(result);

	typedef xr_vector<motion_marks>	Marks;
	Marks const			&marks = motion_def->marks;
	if (marks.size() < 3) {
		current_operator()->on_no_mark	();
		return			(result);
	}

	float				previous_time = m_previous_time;
	float				time_current = blend->timeCurrent + .1f;
	m_previous_time		= time_current;
	// Slipch told me, that timeCurrent can decrease (setup to 0.f) during animation playing
	// therefore we need here to clamp previous value
	clamp				(previous_time, 0.f, time_current);
	// first 2 should be footsteps
	if (!marks[2].is_mark_between(previous_time, time_current)) {
		current_operator()->on_no_mark	();
		return			(result);
	}

	current_operator()->on_mark	();
	return				(result);
}

void animation_selector::on_animation_end			()
{
	VERIFY				(!m_callback_called);
	m_callback_called	= true;
}

void animation_selector::modify_animation			(CBlend* blend)
{
	if (!blend)
		return;

	CMotionDef			*motion_def = m_skeleton_animated->LL_GetMotionDef(blend->motionID);
	VERIFY				(motion_def);
	blend->speed		= motion_def->Speed()*g_smart_cover_animation_speed_factor;
}

void animation_selector::save						(NET_Packet &packet)
{
	m_planner->save		(packet);
}

void animation_selector::load						(IReader &packet)
{
	m_planner->load		(packet);
}

void animation_selector::setup						(CAI_Stalker *object, CPropertyStorage *storage)
{
	m_planner->setup	(object, storage);
}